﻿using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.IO.Compression;
using System.Runtime.Serialization;

namespace Titan.Serialization
{
    internal static class DataSerializer
    {
        private static ObjectReaderWriter[] CreateReaderWriters(DataTable table)
        {
            ObjectReaderWriter[] retval = new ObjectReaderWriter[table.Columns.Count];
            for (int i = 0; i < table.Columns.Count; i++)
                retval[i] = ObjectReaderWriter.Create(table.Columns[i].DataType);
            return retval;
        }

        private static byte[] DumpSchema(DataTable table)
        {
            MemoryStream stream = new MemoryStream();
            table.WriteXmlSchema(stream);
            stream.Seek(0, SeekOrigin.Begin);
            return stream.ToArray();
        }

        private static byte[] DumpSchema(DataSet dataSet)
        {
            MemoryStream stream = new MemoryStream();
            dataSet.WriteXmlSchema(stream);
            stream.Seek(0, SeekOrigin.Begin);
            return stream.ToArray();
        }

        private static void InternalDumpData(DataTable table, BinaryWriter writer)
        {
            ObjectReaderWriter[] rw = CreateReaderWriters(table);

            int columnCount = table.Columns.Count;
            int rowCount = table.Rows.Count;

            DataRowCollection rows = table.Rows;

            writer.Write(rowCount);

            for (int i = 0; i < rowCount; i++)
            {
                object[] itemArray = rows[i].ItemArray;
                for (int j = 0; j < columnCount; j++)
                    rw[j].Write(writer, itemArray[j]);
            }
        }

        private static byte[] DumpData(DataTable table)
        {
            MemoryStream stream = new MemoryStream();
            BinaryWriter writer = new BinaryWriter(stream);
            InternalDumpData(table, writer);
            writer.Flush();
            stream.Seek(0, SeekOrigin.Begin);
            return stream.ToArray();
        }

        private static byte[] DumpData(DataSet dataSet)
        {
            MemoryStream stream = new MemoryStream();
            BinaryWriter writer = new BinaryWriter(stream);

            writer.Write(dataSet.Tables.Count);

            for (int i = 0; i < dataSet.Tables.Count; i++)
            {
                InternalDumpData(dataSet.Tables[i], writer);
            }

            writer.Flush();
            stream.Seek(0, SeekOrigin.Begin);
            return stream.ToArray();
        }

        private static void LoadSchema(DataTable table, byte[] schema)
        {
            MemoryStream stream = new MemoryStream(schema);
            table.ReadXmlSchema(stream);
        }

        private static void LoadSchema(DataSet dataSet, byte[] schema)
        {
            MemoryStream stream = new MemoryStream(schema);
            dataSet.ReadXmlSchema(stream);
        }

        private static void InternalLoadData(DataTable table, BinaryReader reader)
        {
            ObjectReaderWriter[] rw = CreateReaderWriters(table);

            int columnCount = table.Columns.Count;
            int rowCount = reader.ReadInt32();
            DataRowCollection rows = table.Rows;

            object[] buff = new object[columnCount];

            table.BeginLoadData();
            try
            {
                while (rowCount > 0)
                {
                    for (int i = 0; i < columnCount; i++)
                        buff[i] = rw[i].Read(reader);
                    rows.Add(buff);
                    rowCount--;
                }
            }
            finally
            {
                table.EndLoadData();
            }
        }

        private static void LoadData(DataTable table, byte[] data)
        {
            MemoryStream stream = new MemoryStream(data);
            BinaryReader reader = new BinaryReader(stream);
            InternalLoadData(table, reader);
        }

        private static void LoadData(DataSet dataSet, byte[] data)
        {
            MemoryStream stream = new MemoryStream(data);
            BinaryReader reader = new BinaryReader(stream);

            int tableCount = reader.ReadInt32();

            for (int i = 0; i < tableCount; i++)
            {
                InternalLoadData(dataSet.Tables[i], reader);
            }
        }

        private static byte[] Compress(byte[] buff)
        {
            MemoryStream compressed = new MemoryStream(SizeLimitToBeCompressed);
            DeflateStream stream = new DeflateStream(compressed, CompressionMode.Compress, true);
            stream.Write(buff, 0, buff.Length);
            stream.Close();
            compressed.Seek(0, SeekOrigin.Begin);
            return compressed.ToArray();
        }

        private static byte[] Decompress(byte[] compressedBuff)
        {
            DeflateStream stream = new DeflateStream(new MemoryStream(compressedBuff), CompressionMode.Decompress);
            MemoryStream decompressed = new MemoryStream(SizeLimitToBeCompressed);
            int buffSize = 4096;
            byte[] buff = new byte[buffSize];

            int count = stream.Read(buff, 0, buffSize);

            while (count > 0)
            {
                decompressed.Write(buff, 0, count);
                count = stream.Read(buff, 0, buffSize);
            }

            decompressed.Seek(0, SeekOrigin.Begin);
            return decompressed.ToArray();
        }

        public static void Serialize(DataSet dataSet, SerializationInfo info, StreamingContext context)
        {
            byte[] schema = DumpSchema(dataSet);
            byte[] data = DumpData(dataSet);
            bool compressed = data.Length >= SizeLimitToBeCompressed;

            if (compressed)
                data = Compress(data);

            info.AddValue("#schema", schema, typeof(byte[]));
            info.AddValue("#data", data, typeof(byte[]));
            info.AddValue("#compressed", compressed, typeof(bool));
        }

        public static void Deserialize(DataSet dataSet, SerializationInfo info, StreamingContext context)
        {
            byte[] schema = (byte[])info.GetValue("#schema", typeof(byte[]));
            byte[] data = (byte[])info.GetValue("#data", typeof(byte[]));
            bool compressed = (bool)info.GetValue("#compressed", typeof(bool));

            if (compressed)
                data = Decompress(data);

            LoadSchema(dataSet, schema);
            LoadData(dataSet, data);
        }

        public static void Serialize(DataTable table, SerializationInfo info, StreamingContext context)
        {
            byte[] schema = DumpSchema(table);
            byte[] data = DumpData(table);
            bool compressed = data.Length >= SizeLimitToBeCompressed;

            if (compressed)
                data = Compress(data);

            info.AddValue("#schema", schema, typeof(byte[]));
            info.AddValue("#data", data, typeof(byte[]));
            info.AddValue("#compressed", compressed, typeof(bool));
        }

        public static void Deserialize(DataTable table, SerializationInfo info, StreamingContext context)
        {
            byte[] schema = (byte[])info.GetValue("#schema", typeof(byte[]));
            byte[] data = (byte[])info.GetValue("#data", typeof(byte[]));
            bool compressed = (bool)info.GetValue("#compressed", typeof(bool));

            if (compressed)
                data = Decompress(data);

            LoadSchema(table, schema);
            LoadData(table, data);
        }

        private static readonly int SizeLimitToBeCompressed = 1024 * 100; //100K
    }
}
